﻿using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using System;
using System.Reflection;
using Utility.Math;

#if UNITY_EDITOR
namespace Utility.Inspector
{
	public static class EditorGUILayoutEX
	{
		static float LineHeight => EditorGUIUtility.singleLineHeight;

		#region gui
		public static void DrawDivider ()
		{
			DrawDivider (Color.white);
		}

		public static void DrawDivider (Color color, int thickness = 2, int paddding = 10)
		{
			Rect rect = EditorGUILayout.GetControlRect (GUILayout.Height (paddding + thickness));
			rect.height = thickness;
			rect.y += paddding / 2;
			rect.x -= 2;
			rect.width += 6;
			EditorGUI.DrawRect (rect, color);
		}

		public static void SeparatorLine (int thickness = 2, int paddding = 10)
		{
			Rect rect = EditorGUILayout.GetControlRect (GUILayout.Height (paddding + thickness));
			rect.height = thickness;
			rect.y += paddding / 2;
			rect.x -= 2;
			rect.width += 6;
			GUI.Box (rect, "");
		}

		public static T[] DrawObjectArray<T> (T[] array)
			where T : UnityEngine.Object
		{
			//length
			array = array ?? new T[1];
			int oldLength = array.Length;
			int length = EditorGUILayout.DelayedIntField ("Count", oldLength);
			if (length != oldLength)
			{
				T[] newArray = new T[length];
				Array.Copy (array, 0, newArray, 0, length < oldLength ? length : oldLength);
				array = newArray;
			}
			//items
			EditorGUI.indentLevel++;
			for (int i = 0; i < length; i++)
			{
				array[i] = (T)EditorGUILayout.ObjectField (array[i], typeof (T), false);

			}
			EditorGUI.indentLevel--;
			return array;
		}
		#endregion
		#region grid
		static int Clamp (int val, int min, int max)
		{
			if (min > max)
			{
				throw new Exception ("min > max");
			}
			val = val < min ? min : (val > max ? max : val);
			return val;
		}

		public static GridData DrawGrid<T> (GridData gridData, int itemsPerRow, IList<T> datas, Func<T, int, T> drawer, GridSetting? gridSetting = null)
		{
			GridSetting setting = gridSetting.HasValue ? gridSetting.Value : GridSetting.Default;

			int startIndex = Clamp (setting.startIndex, 0, datas.Count - 1);
			int endIndex = setting.endIndex < 0 ? datas.Count - 1 : setting.endIndex;
			endIndex = Clamp (endIndex, startIndex, datas.Count - 1);

			//determine use pagination or not
			bool usePagination = setting.rowsPerPage > 0;
			int totalPages = 0;
			if (usePagination)
			{
				int count = endIndex + 1 - startIndex;
				int rows = Mathf.CeilToInt (count / (float)itemsPerRow);
				if (rows > setting.rowsPerPage)
				{
					int itemsPerPage = itemsPerRow * setting.rowsPerPage;
					totalPages = Mathf.CeilToInt (count / (float)itemsPerPage);

					startIndex = startIndex + (gridData.pageNumber - 1) * itemsPerPage;
					startIndex = Clamp (startIndex, 0, datas.Count - 1);

					endIndex = startIndex + itemsPerPage - 1;
					endIndex = Clamp (endIndex, startIndex, datas.Count - 1);
				}
				else
				{
					usePagination = false;
					gridData.pageNumber = 1;
				}
			}

			//draw pagination
			if (usePagination)
			{
				gridData.pageNumber = DrawPagination (gridData.pageNumber, totalPages);
			}

			using (var v_group = VerticalScope.New ())
			{
				//record grid rect
				if (Event.current.type == EventType.Repaint)
				{
					gridData.gridRect = v_group.Rect;
				}
				float cellWidth = gridData.gridRect.width / itemsPerRow;
				int index = startIndex;
				while (index <= endIndex)
				{
					using (HorizontalScope.New ())
					{
						for (int i = 0; i < itemsPerRow; i++)
						{
							using (VerticalScope.New (GUILayout.MaxWidth (cellWidth)))
							{
								if (index <= endIndex)
								{
									datas[index] = drawer (datas[index], index);
								}
								index++;
							}
						}
					}
				}
			}

			return gridData;
		}

		#endregion

		/// <summary>
		/// draw pagination, page number is in range [1, maxPage]
		/// </summary>
		/// <param name="pageNumber"></param>
		/// <param name="maxPage"></param>
		/// <returns></returns>
		public static int DrawPagination (int pageNumber, int maxPage)
		{
			using (HorizontalScope.New (FlexibleTypes.Begin))
			{
				if (GUILayout.Button ("<", GUILayout.ExpandWidth (false)))
				{
					pageNumber--;
				}
				float numFieldWidth = Mathf.FloorToInt (Mathf.Log10 (maxPage)) * 10 + 10;
				pageNumber = EditorGUILayout.DelayedIntField (pageNumber, GUILayout.Width (numFieldWidth));
				EditorGUILayout.LabelField ("/" + maxPage, GUILayout.Width (numFieldWidth + 10));
				if (GUILayout.Button (">", GUILayout.ExpandWidth (false)))
				{
					pageNumber++;
				}
			}
			pageNumber = pageNumber < 1 ? 1 : pageNumber;
			pageNumber = pageNumber > maxPage ? maxPage : pageNumber;
			return pageNumber;
		}
		#region object field
		public static T ObjectField<T> (GUIContent label, T obj, bool allowSceneObjects)
			where T : UnityEngine.Object
		{
			obj = EditorGUILayout.ObjectField (label, obj, typeof (T), allowSceneObjects) as T;
			return obj;
		}

		public static T ObjectField<T> (string label, T obj, bool allowSceneObjects)
			where T : UnityEngine.Object
		{
			obj = EditorGUILayout.ObjectField (label, obj, typeof (T), allowSceneObjects) as T;
			return obj;
		}
		#endregion
		#region custom object field
		//custom button
		public static T CustomObjectField<T> (GUIContent label, T obj, bool allowSceneObjects, out bool btnClicked)
			where T : UnityEngine.Object
		{
			Rect position = GUILayoutUtility.GetRect (0, LineHeight, EditorStyles.textField);
			return EditorGUIEX.CustomObjectField (position, label, obj, allowSceneObjects, out btnClicked);
		}

		public static T CustomObjectField<T> (string label, T obj, bool allowSceneObjects, out bool btnClicked)
			where T : UnityEngine.Object
		{
			return CustomObjectField (new GUIContent (label), obj, allowSceneObjects, out btnClicked);
		}

		//custom object picker
		public static T CustomObjectField<T> (GUIContent label, T obj, bool allowSceneObjects, string searchFilter)
			where T : UnityEngine.Object
		{
			Rect position = GUILayoutUtility.GetRect (0, LineHeight, EditorStyles.textField);
			return EditorGUIEX.CustomObjectField (position, label, obj, allowSceneObjects, searchFilter);
		}

		public static T CustomObjectField<T> (string label, T obj, bool allowSceneObjects, string searchFilter)
			where T : UnityEngine.Object
		{
			return CustomObjectField (new GUIContent (label), obj, allowSceneObjects, searchFilter);
		}
		#endregion
		#region toggle button
		public static bool ToggleButton (GUIContent label, bool value)
		{
			return EditorGUILayout.Toggle (label, value, GUI.skin.button);
		}

		public static bool ToggleButton (string label, bool value)
		{
			return ToggleButton (new GUIContent (label), value);
		}
		#endregion
		#region string popup
		public static string StringPopupField (string label, string value, string[] values, bool editable = false)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.StringPopupField (position, label, value, values, editable);
		}

		public static string StringPopupField (string label, string value, string[] values, string[] showNames)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.StringPopupField (position, label, value, values, showNames);
		}
		#endregion
		#region nullable
		//float
		public static float? NullabelFloatField (GUIContent label, float? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableFloatField (position, label, value);
		}

		public static float? NullabelFloatField (string label, float? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableFloatField (position, label, value);
		}

		//float
		public static int? NullabelIntField (GUIContent label, int? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableIntField (position, label, value);
		}

		public static int? NullabelIntField (string label, int? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableIntField (position, label, value);
		}

		//bool
		public static bool? NullabelBoolField (GUIContent label, bool? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableBoolField (position, label, value);
		}

		public static bool? NullabelBoolField (string label, bool? value)
		{
			Rect position = GUILayoutUtility.GetRect (0, EditorGUIUtility.singleLineHeight, EditorStyles.textField);
			return EditorGUIEX.NullableBoolField (position, label, value);
		}
		#endregion
		#region enum
		public static TEnum EnumPopup<TEnum> (string label, TEnum value)
			where TEnum : Enum
		{
			return (TEnum)EditorGUILayout.EnumPopup (label, value);
		}

		#endregion
		#region button
		public static bool RefreshButton ()
		{
			return GUILayout.Button (EditorGUIEX.Icon_Refresh, GUILayout.Height (EditorGUIUtility.singleLineHeight));
		}
		#endregion
		#region text
		public static string TextArea (string label, string text, int lines)
		{
			EditorGUILayout.LabelField (label);
			return EditorGUILayout.TextArea (text, GUILayout.Height (lines * EditorGUIUtility.singleLineHeight));
		}
		#endregion
		#region decimal
		public static decimal DecimalField (string label, decimal value)
		{
			Rect position = GUILayoutUtility.GetRect (0, LineHeight, EditorStyles.textField);
			return EditorGUIEX.DecimalField (position, label, value);
		}
		#endregion

	
	}

	public struct GridSetting
	{
		public static readonly GridSetting Default = new GridSetting ()
		{
			startIndex = 0,
			endIndex = -1,
			rowsPerPage = -1,
		};

		public int startIndex;
		public int endIndex;

		public int rowsPerPage;

	}

	public struct GridData
	{
		public int pageNumber;
		public Rect gridRect;
	}
}
#endif